package sm.util;

import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sm.codec.type.Logical;
import sm.codec.type.NullValue;
import sm.codec.type.Operator;
import sm.codec.type.RC;
import sm.codec.type.Struct;
import sm.codec.type.Variable;
import sm.dbdict.def.DBDict;
import sm.def.Type;

public class Util {
	private static Logger logger = LoggerFactory.getLogger(Util.class);

	private static final DateFormat abs_df = new SimpleDateFormat(
			"MM/dd/yy HH:mm:ss");

	private static final DateFormat rel_df = new SimpleDateFormat(
			"DDD HH:mm:ss");

	private static final DateFormat abbr_rel_df = new SimpleDateFormat(
			"HH:mm:ss");

	private static final NumberFormat nf = NumberFormat.getInstance();

	/**
	 * treat NullValue and empty array to be null
	 * 
	 * @param obj
	 * @return
	 */
	public static boolean isNull(Object obj) {
		boolean isNull = false;
		if (obj == null || obj instanceof NullValue) {
			isNull = true;
		} else {
			if (obj instanceof List) {
				List<Object> list = (List<Object>) obj;
				if (list.isEmpty()) {
					isNull = true;
				}
			}
		}
		return isNull;
	}

	/**
	 * compare two entities include NULL
	 * 
	 * @param oldObj
	 * @param newObj
	 * @return
	 */
	public static boolean equals(Object oldObj, Object newObj) {
		if (isNull(oldObj)) {
			if (isNull(newObj)) {
				return true;
			} else {
				return false;
			}
		} else {
			if (isNull(newObj)) {
				return false;
			} else {
				return oldObj.equals(newObj);
			}
		}
	}

	/**
	 * convert a value to its string format stores in array
	 * 
	 * @param value
	 * @return
	 */
	public static String toString(Object value) {
		return toString(value, Type.getTypeNumber(value));
	}

	/**
	 * convert a value to its string format according to its type
	 * 
	 * @param value
	 * @param type
	 * @return
	 */
	public static String toString(Object value, int type) {
		String valueStr = null;
		if (value != null && !(value instanceof NullValue)) {
			switch (type) {
			case Type.NUMBER_TYPE:
				valueStr = nf.format(value);
				break;
			case Type.CHARACTER_TYPE:
				valueStr = value.toString();
				break;
			case Type.TIME_TYPE:
				Date date = (Date) value;
				valueStr = abs_df.format(date);
				break;
			case Type.LOGICAL_TYPE:
				Logical logical = (Logical) value;
				if (logical == Logical.TRUE) {
					valueStr = "t";
				} else if (logical == Logical.FALSE) {
					valueStr = "f";
				} else if (logical == Logical.UNKNOWN) {
					valueStr = "u";
				}
			}
		}
		return valueStr;
	}

	/**
	 * parse a string stored in array
	 * 
	 * @param s
	 * @param type
	 * @return
	 */
	public static Object parse(String s, int type) {
		Object result = null;
		try {
			switch (type) {
			case Type.NUMBER_TYPE:
				Number number = nf.parse(s);
				result = new Double(number.doubleValue());
				break;
			case Type.CHARACTER_TYPE:
				result = s;
				break;
			case Type.TIME_TYPE:
				try {
					result = abs_df.parse(s);
				} catch (ParseException e) {
					try {
						result = rel_df.parse(s);
					} catch (ParseException e1) {
						result = abbr_rel_df.parse(s);
					}
				}
				break;
			case Type.LOGICAL_TYPE:
				if ("t".equalsIgnoreCase(s)) {
					result = Logical.TRUE;
				} else if ("f".equalsIgnoreCase(s)) {
					result = Logical.FALSE;
				} else if ("u".equals(s)) {
					result = Logical.UNKNOWN;
				}
				break;
			}
		} catch (ParseException e) {
			logger.warn(e.getMessage());
		}
		return result;
	}

	/**
	 * clone a data
	 * 
	 * @param data
	 * @return
	 */
	public static Object cloneData(Object data) {
		Object clonedData = data;
		if (data instanceof Date) {
			Date cData = (Date) data;
			clonedData = cData.clone();
		} else if (data instanceof Variable) {
			Variable cData = (Variable) data;
			clonedData = cData.clone();
		} else if (data instanceof Operator) {
			Operator cData = (Operator) data;
			clonedData = cData.clone();
		} else if (data instanceof List) {
			List<Object> cDataList = (List<Object>) data;
			List<Object> clonedDataList = new ArrayList<Object>();
			for (Object cData : cDataList) {
				clonedDataList.add(Util.cloneData(cData));
			}
			clonedData = clonedDataList;
		} else if (data instanceof SMMap) {
			SMMap cData = (SMMap) data;
			clonedData = cData.clone();
		} else if (data instanceof DBDict) {
			DBDict cData = (DBDict) data;
			clonedData = cData.clone();
		} else if (data instanceof RC) {
			RC cData = (RC) data;
			clonedData = cData.clone();
		} else if (data instanceof Struct) {
			Struct cData = (Struct) data;
			clonedData = cData.clone();
		}

		return clonedData;
	}

	private static final String[] radKeyWords = new String[] { "Object",
			"class" };

	private static final String[] javaKeyWords = new String[] { "Object_",
			"class_" };

	/**
	 * convert a rad entity name to java
	 * 
	 * @param radEntityName
	 * @return
	 */
	public static String radEntityNameToJava(String radEntityName) {
		String javaEntityName = radEntityName;
		int length = radKeyWords.length;
		for (int i = 0; i < length; i++) {
			if (radEntityName.equals(radKeyWords[i])) {
				javaEntityName = javaKeyWords[i];
				break;
			}
		}
		return javaEntityName;
	}

	/**
	 * convert a rad field name to java
	 * 
	 * @param radFieldName
	 * @return
	 */
	public static String radFieldNameToJava(String radFieldName) {
		return radEntityNameToJava(radFieldName).replaceAll("\\.", "_");
	}

	/**
	 * convert a rad field path to java
	 * 
	 * @param radFieldPath
	 * @return
	 */
	public static String radFieldPathToJava(String radFieldPath) {
		return radEntityNameToJava(radFieldPath).replaceAll("\\.", "_")
				.replaceAll(",", "\\.");
	}

	/**
	 * convert rad field paths to java
	 * 
	 * @param radFieldPaths
	 * @return
	 */
	public static String[] radFieldPathsToJava(String[] radFieldPaths) {
		int length = radFieldPaths.length;
		String[] javaFieldPaths = new String[length];
		for (int i = 0; i < length; i++) {
			String radFieldPath = radFieldPaths[i];
			javaFieldPaths[i] = radFieldPathToJava(radFieldPath);
		}
		return javaFieldPaths;
	}

	/**
	 * convert a java entity name to rad
	 * 
	 * @param javaEntityName
	 * @return
	 */
	public static String javaEntityNameToRad(String javaEntityName) {
		String radEntityName = javaEntityName;
		int length = javaKeyWords.length;
		for (int i = 0; i < length; i++) {
			if (radEntityName.equals(javaKeyWords[i])) {
				radEntityName = radKeyWords[i];
				break;
			}
		}
		return radEntityName;
	}

	/**
	 * convert a java field name to rad
	 * 
	 * @param javaFieldName
	 * @return
	 */
	public static String javaFieldNameToRad(String javaFieldName) {
		return javaEntityNameToRad(javaFieldName).replaceAll("_", "\\.");
	}

	/**
	 * convert a java field paths to rad
	 * 
	 * @param javaFieldPath
	 * @return
	 */
	public static String javaFieldPathToRad(String javaFieldPath) {
		return javaEntityNameToRad(javaFieldPath).replaceAll("\\.", ",")
				.replaceAll("_", "\\.");
	}

	/**
	 * convert java field paths to rad
	 * 
	 * @param javaFieldPaths
	 * @return
	 */
	public static String[] javaFieldPathsToRad(String[] javaFieldPaths) {
		int length = javaFieldPaths.length;
		String[] radFieldPaths = new String[length];
		for (int i = 0; i < length; i++) {
			String javaFieldPath = javaFieldPaths[i];
			radFieldPaths[i] = javaFieldPathToRad(javaFieldPath);
		}
		return radFieldPaths;
	}

	/**
	 * convert a string logical
	 * 
	 * @param character
	 * @return
	 */
	public static Object characterToLogical(String character) {
		Object result = NullValue.LogicalNull;
		if ("t".equalsIgnoreCase(character)
				|| "true".equalsIgnoreCase(character)
				|| "y".equalsIgnoreCase(character)
				|| "yes".equalsIgnoreCase(character)) {
			result = Logical.TRUE;
		} else if ("f".equalsIgnoreCase(character)
				|| "false".equalsIgnoreCase(character)
				|| "n".equalsIgnoreCase(character)
				|| "no".equalsIgnoreCase(character)) {
			result = Logical.FALSE;
		} else if ("u".equalsIgnoreCase(character)
				|| "unknown".equalsIgnoreCase(character)) {
			result = Logical.UNKNOWN;
		}
		return result;
	}

	/**
	 * convert a key to its string format
	 * 
	 * @param keyValues
	 * @return
	 */
	public static String formatKey(Object[] keys) {
		StringBuilder builder = new StringBuilder();
		builder.append('[');
		int length = keys.length;
		for (int i = 0; i < length; i++) {
			Object key = keys[i];
			if (key instanceof Number) {
				Number keyValue = (Number) key;
				builder.append(Double.toString(keyValue.doubleValue()));
			} else if (key instanceof String) {
				String keyValue = (String) key;
				builder.append('"');
				builder.append(keyValue);
				builder.append('"');
			} else if (key instanceof Date) {
				Date keyValue = (Date) key;
				builder.append('\'');
				builder.append(abs_df.format(keyValue));
				builder.append('\'');
			} else if (key instanceof Logical) {
				Logical keyValue = (Logical) key;
				builder.append(keyValue.getName());
			} else if (key instanceof NullValue) {
				NullValue keyValue = (NullValue) key;
				builder.append(keyValue.getName());
			} else {
				throw new IllegalArgumentException("Can't format key type "
						+ key.getClass().getName());
			}

			if (i < length - 1) {
				builder.append(',');
			}
		}

		builder.append(']');
		return builder.toString();
	}

	/**
	 * parse a key string
	 * 
	 * @param keyStr
	 * @return
	 */
	public static Object[] parseKey(String keysStr) {
		int keyStrsLength = keysStr.length();
		keysStr = keysStr.substring(1, keyStrsLength - 1);
		keyStrsLength = keysStr.length();

		int keyStartIdx = 0;
		int curIdx = 0;
		List<Object> valueList = new ArrayList<Object>();
		while (curIdx < keyStrsLength) {
			String keyStr = null;
			int splitIdx = keysStr.indexOf(',', curIdx);
			if (splitIdx == -1) {
				// the last element
				keyStr = keysStr.substring(keyStartIdx, keyStrsLength);
				curIdx = keyStrsLength;
			} else {
				keyStr = keysStr.substring(keyStartIdx, splitIdx);
				curIdx = splitIdx + 1;
				if (!isKeyStr(keyStr)) {
					continue;
				} else {
					keyStartIdx = curIdx;
				}
			}

			Object keyValue;
			try {
				keyValue = null;
				int keyStrLength = keyStr.length();
				if (keyStrLength > 0) {
					char firstChar = keyStr.charAt(0);
					if (firstChar == '"') {
						keyValue = keyStr.substring(1, keyStrLength - 1);
					} else if (firstChar == '\'') {
						keyStr = keyStr.substring(1, keyStrLength - 1);
						keyValue = abs_df.parse(keyStr);
					} else {
						if (keyStr.endsWith("Null")) {
							keyValue = NullValue.getNullValue(keyStr);
						} else {
							Logical logicalValue = Logical.getLogical(keyStr);
							if (logicalValue != null) {
								keyValue = logicalValue;
							} else {
								// number type
								keyValue = Double.parseDouble(keyStr);
							}
						}
					}

				} else {
					keyValue = keyStr;
				}
			} catch (Exception e) {
				throw new IllegalArgumentException("Can't parse key str "
						+ keysStr, e);
			}

			valueList.add(keyValue);
		}
		return valueList.toArray();
	}

	private static boolean isKeyStr(String keyStr) {
		int quotaCount = 0;
		int curIdx = 0;
		int length = keyStr.length();
		while (curIdx < length) {
			int splitIdx = keyStr.indexOf("\"", curIdx);
			if (splitIdx == -1) {
				curIdx = length;
			} else {
				quotaCount++;
				curIdx = splitIdx + 1;
			}
		}
		return quotaCount % 2 == 0;
	}

}
